using System;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Xml;

using mshtml;
using Pegasus.Web.Html;

namespace Pegasus.Windows.Forms
{
	/// <summary>
	/// This class is a HTML Editor.  It uses the Microsoft HTML Web Browser Control in special edit mode to allow users to
	/// edit HTML content.  
	/// </summary>
	/// <remarks>
	/// <para><b>Deployment Notes:</b></para>
	/// <para>1. Because this control requires a refernece to the Microsoft.MSHTML COM object it was placed in it's 
	/// own assembly so that the main library does not have any external reference to object that are not part of the .NET system
	/// assemblies.</para>
	/// <para>2. Windows Vista did not ship with the MSHTML control as with previous version of the Windows Operating 
	/// Systems.  You must download and install the control on the target system before this control will work properly.  See the MSDN article
	/// <a href="http://msdn2.microsoft.com/en-us/library/aa663363.aspx" target="_parent">Replacing the DHTML Editing Control in Windows Vista and Beyond</a> for
	/// more information on the subject.</para>
	/// </remarks>
	public partial class HtmlEditor : UserControl
	{
		// Local Const Values
		private const string DEFAULT_DOCUMENT = "<HTML><BODY>{0}</BODY></HTML>";
		
		/// <summary>
		/// Initializes a new instance of the <see cref="HtmlEditor"/> class.
		/// </summary>
		public HtmlEditor()
		{
			InitializeComponent();

			// Give the editor a default document and put it in design mode
			WebEditor.DocumentText = string.Format( DEFAULT_DOCUMENT, "" );
			( (IHTMLDocument2) WebEditor.Document.DomDocument ).designMode = "On";
		}

		/// <summary>
		/// Gets or sets the documents HTML.
		/// </summary>
		/// <value>The document text.</value>
		[Browsable( false )]
		public string DocumentHtml
		{
			get
			{
				return WebEditor.DocumentText;
			}

			set
			{
				WebEditor.DocumentText = value;
				SetControlState();
			}
		}

		/// <summary>
		/// Gets or sets the document XHTML.
		/// </summary>
		/// <value>The document X HTML.</value>
		[Browsable( false )]
		public string DocumentXHtml
		{
			get
			{
				string html = DocumentHtml;
				if( !string.IsNullOrEmpty( html ) )
				{
					HtmlReader reader = new HtmlReader( DocumentHtml );
					using( MemoryStream memStream = new MemoryStream() )
					{
						HtmlWriter writer = new HtmlWriter( new StreamWriter( memStream ) );
						while( !reader.EOF )
						{
							writer.WriteNode( reader, true );
						}

						writer.Flush();
						return UTF8Encoding.UTF8.GetString( memStream.GetBuffer(), 0, (int) memStream.Position ).Trim();
					}
				}

				return string.Empty;
			}
		
			set
			{
				DocumentHtml = value;
			}
		}

		/// <summary>
		/// Gets or sets the document XHTML body.
		/// </summary>
		/// <value>The document XHTML body.</value>
		[Browsable( false )]
		public string DocumentXHtmlBody
		{
			get
			{
				string xhtml = DocumentXHtml;
				if( !string.IsNullOrEmpty( xhtml ) )
				{
					XmlDocument xmlDoc = new XmlDocument();

					try
					{
						xmlDoc.LoadXml( DocumentXHtml );
					}
					catch
					{
						return string.Empty;
					}
					
					XmlNode bodyNode = xmlDoc.SelectSingleNode( "/HTML/BODY" );
					if( bodyNode != null )
					{
						return bodyNode.InnerXml;
					}
				}

				return string.Empty;
			}

			set
			{
				DocumentHtml = string.Format( DEFAULT_DOCUMENT, value );
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="HtmlEditor"/> is in bold mode.
		/// </summary>
		/// <value><c>true</c> if bold; otherwise, <c>false</c>.</value>
		[Browsable( false )]
		public bool Bold
		{
			get
			{
				return ( (IHTMLDocument2) WebEditor.Document.DomDocument ).queryCommandState( "Bold" );
			}

			set
			{
				WebEditor.Document.ExecCommand( "Bold", false, null );
				SetControlState();
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="HtmlEditor"/> is in italic mode.
		/// </summary>
		/// <value><c>true</c> if italic; otherwise, <c>false</c>.</value>
		[Browsable( false )]
		public bool Italic
		{
			get
			{
				return ( (IHTMLDocument2) WebEditor.Document.DomDocument ).queryCommandState( "Italic" );
			}

			set
			{
				WebEditor.Document.ExecCommand( "Italic", false, null );
				SetControlState();
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="HtmlEditor"/> is in underline mode.
		/// </summary>
		/// <value><c>true</c> if underline; otherwise, <c>false</c>.</value>
		[Browsable( false )]
		public bool Underline
		{
			get
			{
				return ( (IHTMLDocument2) WebEditor.Document.DomDocument ).queryCommandState( "Underline" );
			}

			set
			{
				WebEditor.Document.ExecCommand( "Underline", false, null );
				SetControlState();
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="HtmlEditor"/> is in strike through mode.
		/// </summary>
		/// <value><c>true</c> if underline; otherwise, <c>false</c>.</value>
		[Browsable( false )]
		public bool StrikeThrough
		{
			get
			{
				return ( (IHTMLDocument2) WebEditor.Document.DomDocument ).queryCommandState( "StrikeThrough" );
			}

			set
			{
				WebEditor.Document.ExecCommand( "StrikeThrough", false, null );
				SetControlState();
			}
		}


		/// <summary>
		/// Gets or sets a value indicating whether [justify left].
		/// </summary>
		/// <value><c>true</c> if [justify left]; otherwise, <c>false</c>.</value>
		[Browsable( false )]
		public bool JustifyLeft
		{
			get
			{
				return ( (IHTMLDocument2) WebEditor.Document.DomDocument ).queryCommandState( "JustifyLeft" );
			}

			set
			{
				WebEditor.Document.ExecCommand( "JustifyLeft", false, null );
				SetControlState();
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether [justify center].
		/// </summary>
		/// <value><c>true</c> if [justify center]; otherwise, <c>false</c>.</value>
		[Browsable( false )]
		public bool JustifyCenter
		{
			get
			{
				return ( (IHTMLDocument2) WebEditor.Document.DomDocument ).queryCommandState( "JustifyCenter" );
			}

			set
			{
				WebEditor.Document.ExecCommand( "JustifyCenter", false, null );
				SetControlState();
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether [justify right].
		/// </summary>
		/// <value><c>true</c> if [justify right]; otherwise, <c>false</c>.</value>
		[Browsable( false )]
		public bool JustifyRight
		{
			get
			{
				return ( (IHTMLDocument2) WebEditor.Document.DomDocument ).queryCommandState( "JustifyRight" );
			}

			set
			{
				WebEditor.Document.ExecCommand( "JustifyRight", false, null );
				SetControlState();
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether [justify full].
		/// </summary>
		/// <value><c>true</c> if [justify full]; otherwise, <c>false</c>.</value>
		[Browsable( false )]
		public bool JustifyFull
		{
			get
			{
				return ( (IHTMLDocument2) WebEditor.Document.DomDocument ).queryCommandState( "JustifyFull" );
			}

			set
			{
				WebEditor.Document.ExecCommand( "JustifyFull", false, null );
				SetControlState();
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether [unordered list].
		/// </summary>
		/// <value><c>true</c> if [unordered list]; otherwise, <c>false</c>.</value>
		[Browsable( false )]
		public bool UnorderedList
		{
			get
			{
				return ( (IHTMLDocument2) WebEditor.Document.DomDocument ).queryCommandState( "InsertUnorderedList" );
			}

			set
			{
				WebEditor.Document.ExecCommand( "InsertUnorderedList", false, null );
				SetControlState();
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether [ordered list].
		/// </summary>
		/// <value><c>true</c> if [ordered list]; otherwise, <c>false</c>.</value>
		[Browsable( false )]
		public bool OrderedList
		{
			get
			{
				return ( (IHTMLDocument2) WebEditor.Document.DomDocument ).queryCommandState( "InsertOrderedList" );
			}

			set
			{
				WebEditor.Document.ExecCommand( "InsertOrderedList", false, null );
				SetControlState();
			}
		}

		/// <summary>
		/// Cuts the selected text to the clipboard.
		/// </summary>
		public void Cut()
		{
			WebEditor.Document.ExecCommand( "Cut", false, null );
			SetControlState();
		}

		/// <summary>
		/// Copys the selected text to the clipboard.
		/// </summary>
		public void Copy()
		{
			WebEditor.Document.ExecCommand( "Copy", false, null );
			SetControlState();
		}

		/// <summary>
		/// Pastes any text from the clipboard.
		/// </summary>
		public void Paste()
		{
			// The Web Control can handle pasting of images but we only want text.
			if( Clipboard.ContainsText() )
			{
				WebEditor.Document.ExecCommand( "Paste", false, null );
			}

			SetControlState();
		}

		/// <summary>
		/// Removes the formatting from the selected text.
		/// </summary>
		public void RemoveFormatting()
		{
			WebEditor.Document.ExecCommand( "RemoveFormat", false, null );
		}

		/// <summary>
		/// Indents this instance.
		/// </summary>
		public void Indent()
		{
			WebEditor.Document.ExecCommand( "Indent", false, null );
		}

		/// <summary>
		/// Outdents this instance.
		/// </summary>
		public void Outdent()
		{
			WebEditor.Document.ExecCommand( "Outdent", false, null );
		}

		/// <summary>
		/// Undoes this instance.
		/// </summary>
		public void Undo()
		{
			WebEditor.Document.ExecCommand( "Undo", false, null );
		}

		/// <summary>
		/// Redoes this instance.
		/// </summary>
		public void Redo()
		{
			WebEditor.Document.ExecCommand( "Redo", false, null );
		}

		/// <summary>
		/// Handles the Tick event of the TimerToolbar control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void TimerToolbar_Tick( object sender, EventArgs args )
		{
			SetControlState();
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonCut control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonCut_Click( object sender, EventArgs args )
		{
			Cut();
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonCopy control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonCopy_Click( object sender, EventArgs args )
		{
			Copy();
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonPaste control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonPaste_Click( object sender, EventArgs args )
		{
			Paste();
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonBold control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonBold_Click( object sender, EventArgs args )
		{
			ToolButtonBold.Checked = !ToolButtonBold.Checked;
			Bold = ToolButtonBold.Checked;
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonItalic control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonItalic_Click( object sender, EventArgs args )
		{
			ToolButtonItalic.Checked = !ToolButtonItalic.Checked;
			Italic = ToolButtonItalic.Checked;
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonUnderline control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonUnderline_Click( object sender, EventArgs args )
		{
			ToolButtonUnderline.Checked = !ToolButtonUnderline.Checked;
			Underline = ToolButtonUnderline.Checked;
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonStrikeThrough control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonStrikeThrough_Click( object sender, EventArgs args )
		{
			ToolButtonStrikeThrough.Checked = !ToolButtonStrikeThrough.Checked;
			StrikeThrough = ToolButtonStrikeThrough.Checked;
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonRemoveFormat control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonRemoveFormat_Click( object sender, EventArgs args )
		{
			RemoveFormatting();
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonJustifyLeft control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonJustifyLeft_Click( object sender, EventArgs args )
		{
			ToolButtonJustifyLeft.Checked = !ToolButtonJustifyLeft.Checked;
			JustifyLeft = ToolButtonJustifyLeft.Checked;
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonJustifyCenter control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonJustifyCenter_Click( object sender, EventArgs args )
		{
			ToolButtonJustifyCenter.Checked = !ToolButtonJustifyCenter.Checked;
			JustifyCenter = ToolButtonJustifyCenter.Checked;
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonJustifyRight control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonJustifyRight_Click( object sender, EventArgs args )
		{
			ToolButtonJustifyRight.Checked = !ToolButtonJustifyRight.Checked;
			JustifyRight = ToolButtonJustifyRight.Checked;
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonJustifyFull control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonJustifyFull_Click( object sender, EventArgs args )
		{
			ToolButtonJustifyFull.Checked = !ToolButtonJustifyFull.Checked;
			JustifyFull = ToolButtonJustifyFull.Checked;

		}

		/// <summary>
		/// Handles the Click event of the ToolButtonListUnordered control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonListUnordered_Click( object sender, EventArgs args )
		{
			ToolButtonListUnordered.Checked = !ToolButtonListUnordered.Checked;
			UnorderedList = ToolButtonListUnordered.Checked;
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonListOrdered control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonListOrdered_Click( object sender, EventArgs args )
		{
			ToolButtonListOrdered.Checked = !ToolButtonListOrdered.Checked;
			OrderedList = ToolButtonListOrdered.Checked;
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonIndent control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonIndent_Click( object sender, EventArgs args )
		{
			Indent();
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonOutdent control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonOutdent_Click( object sender, EventArgs args )
		{
			Outdent();
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonUndo control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonUndo_Click( object sender, EventArgs args )
		{
			Undo();
		}

		/// <summary>
		/// Handles the Click event of the ToolButtonRedo control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void ToolButtonRedo_Click( object sender, EventArgs args )
		{
			Redo();
		}

		/// <summary>
		/// Handles the PreviewKeyDown event of the WebEditor control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="args">The <see cref="System.Windows.Forms.PreviewKeyDownEventArgs"/> instance containing the event data.</param>
		private void WebEditor_PreviewKeyDown( object sender, PreviewKeyDownEventArgs args )
		{
			// This code used to catch the enter and tab key an use them as input
			// to the editor and not to the dialog/form that the editor is on.
			switch( args.KeyCode )
			{
				case Keys.Enter:
					args.IsInputKey = true;
					break;

				case Keys.Tab:
					args.IsInputKey = true;
					if( args.Shift )
					{
						Outdent();
					}
					else
					{
						Indent();
					}
					break;
			}
		}

		/// <summary>
		/// Sets the state of the control.
		/// </summary>
		private void SetControlState()
		{
			// Set the state of the Cut/Copy buttons
			IHTMLSelectionObject htmlSelection = ( (IHTMLDocument2) WebEditor.Document.DomDocument ).selection;
			if( htmlSelection.type != "None" )
			{
				ToolButtonCut.Enabled = true;
				ToolButtonCopy.Enabled = true;
				ToolButtonRemoveFormat.Enabled = true;
			}
			else
			{
				ToolButtonCut.Enabled = false;
				ToolButtonCopy.Enabled = false;
				ToolButtonRemoveFormat.Enabled = false;
			}
		
			// Set the state of the paste button
			ToolButtonPaste.Enabled = Clipboard.ContainsText();

			// Set the state of the text format buttons
			ToolButtonBold.Checked = Bold;
			ToolButtonItalic.Checked = Italic;
			ToolButtonUnderline.Checked = Underline;
			ToolButtonStrikeThrough.Checked = StrikeThrough;

			ToolButtonJustifyRight.Checked = JustifyRight;
			ToolButtonJustifyCenter.Checked = JustifyCenter;
			ToolButtonJustifyLeft.Checked = JustifyLeft;
			ToolButtonJustifyFull.Checked = JustifyFull;

			ToolButtonListUnordered.Checked = UnorderedList;
			ToolButtonListOrdered.Checked = OrderedList;
		}
	}
}
